package net.yters.model;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

import net.yters.model.Environment.AGNT;
import net.yters.model.agents.Agent;
import net.yters.model.agents.DeciderAgent;
import net.yters.model.agents.TextAgent;
import net.yters.model.agents.concrete.ExternalInputAgent;
import net.yters.model.agents.concrete.HeroAgent;
import net.yters.model.agents.concrete.IDMimicingAgent;
import net.yters.model.agents.concrete.RandomAgent;
import net.yters.model.agents.concrete.SemanticsAgent;
import net.yters.model.metric.ClusterRatioMetric;
import net.yters.model.metric.HistoryCollector;
import net.yters.model.metric.Metric;
import net.yters.util.Box;
import net.yters.util.Util;

public class ModelState extends Agent{
	/**
	 * 
	 */
	private static final long serialVersionUID = -5668175462618422645L;
	public Environment f_env = new Environment();
	public ArrayList<Metric> f_metrics = new ArrayList<Metric>(); 
	public Box f_time = new Box(new Integer(0));

	public ArrayList<DeciderAgent> f_agents = new ArrayList<DeciderAgent>();
	public ArrayList<DeciderAgent> f_newAgents = new ArrayList<DeciderAgent>();
	public DeciderAgent f_hero = null;
	public HashMap<ArrayList<Object>, DeciderAgent> f_agentLocation = new HashMap<ArrayList<Object>, DeciderAgent>(); 

	public HistoryCollector f_collector = null;
	public ClusterRatioMetric f_clusterRatioMetric = null;

	@SuppressWarnings("unused")
	public ModelState() {
		// Initialize random enemies
		for(int i = 0; i < 5; i++) {
			RandomAgent enemyAgent = new RandomAgent(this);
			f_agents.add(enemyAgent);
		}

		// Initialize ID mimicing enemies
		if(false){
			IDMimicingAgent enemyAgent = new IDMimicingAgent(this);
			f_agents.add(enemyAgent);

			// TODO HACK this shouldn't be here 
			f_collector = new HistoryCollector(f_agents, f_time);
			f_metrics.add(f_collector);
			f_clusterRatioMetric = new ClusterRatioMetric(f_collector);
			f_metrics.add(f_clusterRatioMetric);
			f_clusterRatioMetric.f_ownerAgent = enemyAgent; // TODO currently code base can only handle one appropriately, change so it can handle more
		} else {
		}

		for(int i = 0; i < 5; i++) {
			SemanticsAgent enemyAgent = new SemanticsAgent(this);
			f_agents.add(enemyAgent);
		}

		// Initialize hero
		f_hero = new HeroAgent(this);
		f_hero.f_hp.f_value = 1.0;
		f_agents.add(f_hero);

		// Put agents in environment
		for(DeciderAgent a : f_agents) {
			f_env.putContent(net.yters.util.Util.disemBoxArray(a.f_position), (Double) a.f_type.f_value);  // TODO tight coupling
		}

		// NOTE this needs to come last in order to capture all the agents in the simulation 
		f_collector = new HistoryCollector(f_agents, f_time);
		f_metrics.add(f_collector);
	}

	public ModelState(ModelState state) throws IOException, ClassNotFoundException {
		ModelState clone = (ModelState) Util.deepClone(state);

		f_env = clone.f_env;
		f_metrics = clone.f_metrics;
		f_time = clone.f_time;

		f_hero = clone.f_hero;
		f_agents = clone.f_agents;
		f_newAgents = clone.f_newAgents;
		// TODO HACK turn the human controlled agent into a random one
		int heroPos = f_agents.indexOf(f_hero);
		if(heroPos != -1) {
			f_hero = new RandomAgent((TextAgent) f_hero);
			f_agents.set(heroPos, f_hero);
			f_newAgents.set(heroPos, f_hero);
		}

		// TODO HACK remove the IDMimicing agent so we don't get an endless recursion
		for(int i = 0; i < f_agents.size(); i++) {
			if(f_agents.get(i) instanceof IDMimicingAgent) {
				ExternalInputAgent replacement = new ExternalInputAgent((TextAgent) f_agents.get(i));
				replacement.f_brains.clear();
				f_agents.set(i, replacement);
			}
		}
		
		f_collector = clone.f_collector;
		f_clusterRatioMetric = clone.f_clusterRatioMetric; 

		f_agentLocation = clone.f_agentLocation;
	}

	public ArrayList<ExternalInputAgent> getExternalInputAgents() {
		ArrayList<ExternalInputAgent> result = new ArrayList<ExternalInputAgent>();
		for(Agent a : f_agents) if(a instanceof ExternalInputAgent) result.add((ExternalInputAgent) a);
		return result;
	}

	public void addAgent(DeciderAgent agent) { // TODO tight coupling
		f_newAgents.add(agent);
	}

	public void removeAgent(DeciderAgent agent) { // TODO tight coupling
		f_newAgents.remove(agent);
	}

	public DeciderAgent lookupAgent(ArrayList<Object> pos) {
		return f_agentLocation.get(pos);
	}

	@SuppressWarnings("unchecked")
	@Override
	public void execute() {
		f_newAgents = (ArrayList<DeciderAgent>) f_agents.clone();
		ArrayList<Object> position = null;

		ArrayList<DeciderAgent> agentsPermutation = (ArrayList<DeciderAgent>) f_agents.clone();
		Collections.shuffle(agentsPermutation);
		for(DeciderAgent a : agentsPermutation) { // TODO this should really be an iterator on a mutable data structure, and get rid of newAgent
			position = net.yters.util.Util.disemBoxArray(a.f_position);

			f_env.putContent(position, new Double(AGNT.EMPT.ordinal()));  // TODO tight coupling
			f_agentLocation.remove(position);

			a.execute();

			if(f_newAgents.contains(a)) { // TODO HACK this should not need to know whether an agent has been removed 
				position = net.yters.util.Util.disemBoxArray(a.f_position);

				// TODO hack to store different properties in one double number
				double raw =
						((Double) a.f_type.f_value * 1.0) + 
						(((Double) a.f_direction.get(0).f_value + 1.0) * 0.1) + 
						(((Double) a.f_direction.get(1).f_value + 1.0) * 0.01);
				
				System.err.println("Calc'd raw: " + raw); // DEBUG
				
				  // TODO tight coupling
				f_env.putContent(position, raw);
				f_agentLocation.put(position, a);
			}
		}

		f_agents = f_newAgents;

		for(Metric metric : f_metrics) {
			metric.execute();
		}

		f_time.f_value = ((Integer) f_time.f_value) + 1;
	}

	@Override
	public String toString() {
		return f_env.toString();
	}
}
